% Exemplo de relatório técnico do IC
% Criado por P.J.de Rezende antes do Alvorecer da História.
% Modificado em 97-06-15 e 01-02-26 por J.Stolfi.
% Last edited on 2003-06-07 21:12:18 by stolfi

% modificado em 1o. de outubro de 2008

\documentclass[11pt,twoside]{article}
\usepackage{techrep-ic}

\usepackage{amsthm}

%%% SE USAR INGLÊS, TROQUE AS ATIVAÇÕES DOS DOIS COMANDOS A SEGUIR:
\usepackage[brazil]{babel}
%% \usepackage[english]{babel}

%%% SE USAR CODIFICAÇÃO LATIN1, TROQUE AS ATIVAÇÕES DOS DOIS COMANDOS A
%%% SEGUIR:
%% \usepackage[latin1]{inputenc}
\usepackage[utf8]{inputenc}

\usepackage{color}
\usepackage{listings}
\lstset{ %
backgroundcolor=\color{white},  % choose the background color. You must add \usepackage{color}
basicstyle=\ttfamily\small,
numberstyle=\small,
keywordstyle=\color{black}\bfseries,
language=Java,                % choose the language of the code
showspaces=false,               % show spaces adding particular underscores
showstringspaces=false,         % underline spaces within strings
showtabs=false,                 % show tabs within strings adding particular underscores
frame=single,                   % adds a frame around the code
tabsize=4,              % sets default tabsize to 2 spaces
captionpos=b,                   % sets the caption-position to bottom
breaklines=true,        % sets automatic line breaking
breakatwhitespace=false,    % sets if automatic breaks should only happen at whitespace
columns=fullflexible,
escapeinside={\%}{)}          % if you want to add a comment within your code
}

\lstnewenvironment{shell}
	{\lstset{language=csh, frame=single}}
	{}

\begin{document}

%%% PÁGINA DE CAPA %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 
% Número do relatório
\TRNumber{01}

% DATA DE PUBLICAÇÃO (PARA A CAPA)
%
\TRYear{10}  % Dois dígitos apenas
\TRMonth{03} % Numérico, 01-12

% LISTA DE AUTORES PARA CAPA (sem afiliações).
\TRAuthor{E. B. Prata \and G. S. de Paula \and P. R. Costa \and R. Dominiquini}

% TÍTULO PARA A CAPA (use \\ para forçar quebras de linha).
\TRTitle{Sub-Projeto 1:\\Multicast e Revisão Bibliográfica}

\TRMakeCover

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% O que segue é apenas uma sugestão - sinta-se à vontade para
% usar seu formato predileto, desde que as margens tenham pelo
% menos 25mm nos quatro lados, e o tamanho do fonte seja pelo menos
% 11pt. Certifique-se também de que o título e lista de autores
% estão reproduzidos na íntegra na página 1, a primeira depois da
% página de capa.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Nomes de autores ABREVIADOS e titulo ABREVIADO,
% para cabeçalhos em cada página.
%
\markboth{Prata, De Paula, Costa e Dominiquini}{Multicast}
\pagestyle{myheadings}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% TÍTULO e NOMES DOS AUTORES, completos, para a página 1.
% Use "\\" para quebrar linhas, "\and" para separar autores.
%
\title{Sub-Projeto 1: Multicast e Revisão Bibliográfica}

\author{
Eduardo Jacob Prata\thanks{Instituto  de Computação, Universidade
Estadual  de Campinas, 13081-970  Campinas,  SP.}  \and
Gustavo de Paula\footnotemark[1] \and
Paulo de Almeida Costa\footnotemark[1] \and
Rafael Baboni Dominiquini\footnotemark[1]}

\date{}

\maketitle

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{abstract} 
Neste artigo descrevemos diversos aspectos relativos ao multicast e broadcast, justificando o seu uso em sistemas distribuidos para evitar o uso de sistemas de nomes e redução de tráfego na rede. Além disso, descrevemos três artigos fundamentais na área de sistemas distribuídos que fazem uso de diretivas de multicast e que serão a base do desenvolvimento dos projetos desta disciplina.
\end{abstract}

\section{Introdução}

Um método simples e mais eficiente para implementação de sistemas distribuídos em uma rede ethernet deve utilizar diretivas de multicast ou broadcast. Através do uso destas diretivas é possível alcançar todos os computadores ligados na rede local utilizando o envio de apenas uma mensagem ao invés de N mensagens na comunicação unicast. Além disso, o uso de broadcast facilita o problema da nomeação e localização de recursos no sistema, já que as entidades do sistema podem receber uma mensagem broadcast e então responder indicando sua presença e seus dados.

Este artigo contém uma explicação concisa sobre multicast e broadcast, elucidando o seu funcionamento e o seu uso na Internet. Além disso, incluímos um resumo de três artigos fundamentais na área de sistemas distribuídos sobre os quais basearemos o desenvolvimento dos projetos da disciplina.

\section{Multicast}

Nesta seção respondemos algumas questões relativas ao multicast e broadcast visando elucidar o que é como funciona este tipo de comunicação. E, por fim, fornecemos uma descrição sucinta sobre o estágio atual do multicast e broadcast no ambiente de internet.

\subsection{Classes de endereços IP}

Os endereços IP foram originalmente repartidos em classes, de acordo com o número de bytes que identificam a rede e o número de bytes que identificam a máquina na rede. Nas próximas seções definimos cada uma das classes e mostramos ainda uma série de endereço especiais. São ao todo 5 classes, porém, a classe E não é mostrada porque corresponde a uma faixa reservada a testes pela IETF (Internet Engineering Task Force).

\subsubsection{Classe A}

Em um endereço IP de classe A, o primeiro byte representa a rede. O bit de sinal (o primeiro bit, da esquerda) tem valor zero, o que significa que há 126 (00000000 à 01111111) possibilidades de redes com 16 milhões de hosts cada. Contudo, a rede 0 (bits que valem 00000000) não existe e o número 127 é reservado para designar a sua máquina. Dentro da classe A, o intervalo 10.0.0.0 – 10.255.255.255 é reservado para uso em redes locais, e pacotes com IPs neste intervalo não são roteados para a internet.

\subsubsection{Classe B}

Em um endereço IP de classe B, os dois primeiros bytes representam a rede. Os dois primeiros bits são 10, o que significa que há 16.384 redes possíveis (10 000000 00000000 ao 10 111111 11111111) com 65.534 hosts cada. As redes disponíveis em classe B são por conseguinte as redes que vão de 128.0.0.0 a 191.255.0.0. Dentro da classe B, o intervalo 172.16.0.1 – 172.31.255.254 é reservado para uso em redes locais, e pacotes com IPs neste intervalo não são roteados para a internet.

\subsubsection{Classe C}

Em um endereço IP de classe C, os três primeiros bytes representam a rede. Os três primeiros bits são 110, que significa que há 2.097.152 redes possíveis com 254 hosts cada. As redes disponíveis em classe C são por conseguinte as redes que vão de 192.0.0.0 a 223.255.255.0. Dentro da classe C, o intervalo 192.168.0.0 – 192.168.255.255 é reservado para uso em redes locais, e pacotes com IPs neste intervalo não são roteados para a internet.

\begin{center}
	\begin{tabular}{| c | c | c | c | c |}
        \hline
        Classe & Início & Fim & Redes possíveis & Máximo de computadores \\	\hline
        A & 10.0.0.0 & 10.255.255.255 & 126 & 16.777.214 \\	\hline
        B &	172.16.0.0 & 172.31.255.255 & 16.384 & 65534 \\	\hline
        C & 192.168.0.0 & 192.168.255.255 & 2.097.152 &	254 \\ 
        \hline
	\end{tabular}
\end{center}

\subsubsection{Classe D}

Endereços IP de classe D são identificados pelos quatro primeiros bits, com valor 1110. Neste caso especial, o endereço IP não identifica uma rede/máquina, mas sim um grupo de multicast. Basta enviar um pacote para o IP escolhido, e todas as máquina do grupo receberão uma cópia do pacote (exceto por eventuais perdas de pacote), sendo que a transmissão destes é feita de forma eficiente através da coordenação semi-automática dos roteadores intermediários.

A única diferença em relação a uma transmissão normal é que as máquinas destinatárias devem se juntar ao grupo desejado explicitamente, a fim de configurar os roteadores para retransmitir o pacote para todos membros do grupo. Essa configuração é feita através do protocolo IGMP, responsável por associar um endereço ao grupo desejado (join). Também é necessário renovar a associação periodicamente, caso contrário as configurações de roteamento são desfeitas.

\subsubsection{Classes Especiais}

Além destas classes, existem outras faixas de endereço reservadas para classes especiais:

\begin{center}
	\begin{tabular}{| c | c |}
		\hline
            0.0.0.0/8 &	Rede corrente \\ \hline
            10.0.0.0/8 & Rede Privada \\ \hline
            14.0.0.0/8 & Rede Pública \\ \hline
            39.0.0.0/8 & Reservado \\ \hline
            127.0.0.0/8 & Localhost \\ \hline
            128.0.0.0/16 & Reservado (IANA) \\ \hline
            169.254.0.0/16 & Zeroconf \\ \hline
            172.16.0.0/12 &	Rede Privada \\ \hline
            191.255.0.0/16 & Reservado (IANA) \\ \hline
            192.0.2.0/24 & Documentação \\ \hline
            192.88.99.0/24 & IPv6 para IPv4 \\ \hline
            192.168.0.0/16 & Rede Privada \\ \hline
            198.18.0.0/15 & Teste de benchmark de redes \\ \hline
            223.255.255.0/24 & Reservado \\ \hline
            224.0.0.0/4	& Multicasts (antiga rede Classe D) \\ \hline
            240.0.0.0/4	 & Reservado (antiga rede Classe E) \\ \hline
            255.255.255.255 & Broadcast \\ 
            \hline
	\end{tabular}
\end{center}

\subsubsection{Máscara de rede}

Atualmente, a separação entre rede e máquina não é feita apenas através das classes A, B e C, além do IP, usa-se uma máscara de rede escrita no mesmo formato do endereço IP. Ela possui 1 nos bits que identificam a rede e 0 nos bits que identificam a máquina. Alternativamente, a máscara também pode ser especificada apenas pelo número de bits que identificam a rede. Por exemplo, se temos o endereço 192.168.56.100, com máscara 255.255.255.0, os primeiros 24 bits do endereço identificam a rede (192.168.56.0) e os 8 demais identificam a máquina. Também se escreve como 192.168.56.100/24.

\subsection{A relação entre o broadcast e o tráfego na Ethernet}

Ao se utilizar broadcast sobre Ethernet, apenas uma mensagem precisa ser enviada para que todas as máquinas que compartilham o meio possam recebê-la. Desta forma, uma mensagem endereçada apenas a uma máquina, causa o mesmo tráfego de rede que uma mensagem em broadcast. Caso a mensagem seja endereçada a mais máquinas, o trafego em unicast cresce linearmente, enquanto o tráfego em broadcast permanece constante. Portanto o uso de mensagens de broadcast não causa aumento do tráfego de rede como um todo. Na verdade, o trafego permanece constante ou diminui.

Porém, devemos considerar o caso das redes ethernet comutadas, isto é, com meio físico não compartilhado, que é o caso mais comum atualmente. Nestas, uma mensagem em unicast congestiona apenas os segmentos de rede necessários para transportar o pacote ao seu destino. Já uma mensagem em broadcast irá congestionar todos os segmentos de rede. Para as máquinas de origem e destino do pacote, esse tráfego extra não faz diferença em relação a uma rede de meio compartilhado, pois os pacotes precisam trafegar  o segmento de rede correspondente de qualquer maneira. Mas para as máquinas que não estão interessadas naqueles dados, o uso de broadcast congestiona desnecessáriamente os seus segmentos de rede. 

Portanto, em uma rede ethernet comutada, o uso de multicast irá diminuir o congestionamento para os computadores interessados na transmissão, mas irá congestionar os desnecessáriamente os computadores que não precisariam recebê-la.

\subsection{Comunicação ponto-a-ponto na Ethernet}

\emph{PPPoE} (Point-to-Point Protocol over Ethernet) é um protocolo de rede usado para encapsular quadros PPP (Point-to-Point Protocol) dentro de quadros Ethernet. Seu uso típico é nas conexões de um ou múltiplos usuários em uma rede LAN à Internet. Seu principal uso está relacionado à utilização de linhas DSL, onde o PPPoE encapsula os dados de um computador até a central telefônica.

Derivado do PPP, permite acesso autenticado e transmissão de pacotes de diversos protocolos. Para a Internet, o PPP encapsula o protocolo TCP/IP.

\subsection{Obtenção de endereço broadcast em UNIX}

O comando ifconfig deve ser utilizado para obter informações de rede. Entre outras informações, podemos ver se a interface de rede suporta envio de pacotes em broadcast pela presença da flag BROADCAST, e o endereço de broadcast da rede local pelo campo Bcast:

\begin{center}
\begin{shell}
$ ifconfig
eth1    Link encap:Ethernet  HWaddr 00:1C:C0:1C:B1:F6  
		inet addr:143.106.16.197  Bcast:143.106.16.255  Mask:255.255.255.192
        inet6 addr: fe80::21c:c0ff:fe1c:b1f6/64 Scope:Link
        UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
        RX packets:173922 errors:0 dropped:2 overruns:0 frame:0
        TX packets:202640 errors:66873 dropped:0 overruns:0 carrier:66873
        collisions:78219 txqueuelen:100 
        RX bytes:132274542 (126.1 MiB)  TX bytes:191288233 (182.4 MiB)
        Memory:e2200000-e2220000 
\end{shell}
\end{center}

Alternativamente, pode-se utilizar ao invés do endereço broadcast determinado pelo administrador da rede, o endereço 255.255.255.255, que é um endereço especial para enviar broadcasts na rede local.

\subsection{Broadcast nos laboratórios do IC}

Cada um dos laboratórios possui um endereço de rede distinto e um endereço de broadcast distinto. A sala 303 possui endereço 143.106.16.192/26, broadcast 143.106.16.255, a sala 304 possui endereço 143.106.16.0/26, broadcast 143.106.16.63 e a rede onde está a máquina Xaveco possui endereço de rede 143.106.16.128/26, broadcast 143.106.16.191. Para fazer os testes desenvolvemos uma classe java para o envio e recebimento das diretivas de Broadcast e Multicast, esta classe pode ser consultada no diretório do projeto.

Como as redes e os endereços de broadcast são distintos, não foi possível enviar um pacote que fosse entregue em todas as salas.  Mas foi possível enviar um pacote de broadcast de uma sala para outra, isto é, enviado da sala 303 para o endereço de broadcast da sala 304, e recebido com sucesso na outra sala. Dessa forma, se for necessário enviar um pacote para todas as máquinas de algumas salas, só será necessário enviar um pacote por sala utilizando broadcast, ao invés de um pacote por máquina, como seria necessário com Unicast.

Tentamos contornar essa restrição utilizando endereços de multicast, mas estes não parecem funcionar dentro da rede do IC, os pacotes enviados são recebidos apenas pela máquina que os enviou. O mesmo código funciona em outras redes, portanto acreditamos que seja uma restrição de segurança do IC.

Também tentamos enviar pacotes de broadcast a partir de computadores remotos, mas a configuração das redes do IC impediu o recebimento dos pacotes. E, finalmente, percebemos que os elementos da rede do IC permitem o envio de mensagens broadcast pelo simples fato de um pacote com endereço broadcast conseguir passar através dos switches e roteadores internos. A única medida tomada para não sobrecarregar a rede foi utilizar endereços broadcast diferentes em cada sub-rede e, portanto, caso os endereços de broadcast fossem iguais em todas as sub-redes seria possível atingir todas as máquinas da rede com qualquer mensagem broadcast.

\subsection{Multicast e Broadcast na Internet}

\subsubsection{Broadcast}

Uma mensagem em Broadcast é destinada a todos os computadores da rede. Na camada física, usa-se o endereço MAC ff:ff:ff:ff:ff:ff para indicar que um quadro de broadcast. Quando um switch recebe um pacote com destino para esse endereço, ele envia esse pacote para todas as suas portas, de forma que todas as máquinas da rede recebam a mensagem.

Na camada de rede, o ultimo endereço IP de uma sub-rede é reservado para o broadcast. Quando um pacote IP é enviado para esse endereço, ele é roteado normalmente até a sub-rede, onde é enviado para todas as máquinas.

O problema do broadcast é criar um tráfego muito grande na sub-rede ao replicar a mesma mensagem diversas vezes. Quando confinado a uma rede local, esse tráfego é inofensivo, e, uma vez que não requer configurações adicionais, mensagens de broadcast são comunmente utilizadas para descoberta e configuração de serviços na rede.

Porém, caso fosse possível enviar um pacote de broadcast para toda a internet, a retransmissão do mesmo em todas as sub-redes não seria escalável. Por essa razão, é impossível enviar um pedido de broadcast para "toda a internet". Na camada física, um quadro de broadcast não pode ser roteado para outras redes, ficando confinado à rede local. Na camada de rede, todo endereço IP de broadcast está associado a uma sub-rede.

Em IPv6, o suporte a broadcast foi removido. Em substituição, existe um grupo de multicast reservado para a rede local, que é, para todos os efeitos, equivalente.

\subsubsection{Multicast}

Uma mensagem em Multicast é destinada a um grupo de máquinas. Uma das principais aplicações de multicast na internet é de streaming, por exemplo, de programas de TV e de rádio, video-conferencias, etc.

O grande problema dessas aplicações é a necessidade de uma grande banda. Se a empresa que oferece o serviço utilizasse unicast, o mesmo conteúdo precisaria ser enviado a todos os usuários, causando um congestionamento de rede muito grande próximo ao servidor, e broadcast não pode se usado para transmissão para vários destinatários fora da rede local.

Para estas aplicações, foi criado o multicast. Nesta abordagem, os dados não são transmitidos aos destinatários finais, mas sim para um IP especial que representa um grupo de multicast. Quando uma máquina desejar receber estes dados (para assistir o canal de TV, ouvir a rádio, etc), ela envia uma mensagem especial para se juntar ao grupo (Join). Esta mensagem, no protocolo IGMP, é responsável por configurar todos os roteadores entre a máquina e o servidor para que ela receba uma cópia do fluxo multicast.

A fim de retornar as configurações para o normal, a máquina pode enviar uma mensagem para deixar o grupo (leave) explicitamente. Mas como ainda haveria a possibilidade da máquinas ser desligada/desconectada/etc antes de fazê-lo, existe um mecanismo de timeout. Se a máquina passar muito tempo sem renovar seu interesse no grupo, as configurações de roteamento são removidas automaticamente. Após feita a configuração, o servidor apenas envia um pacote para o endereço do grupo, e este é retransmitido de forma eficiente apenas nas conexões necessárias para que o pacote seja recebido por todos os destinatários. 

Mesmo com todos estes benefícios, o uso de multicast nunca foi largamente adotado na internet. Entre as pricipais razões, provavelmente temos:
\begin{itemize}
	\item Um endereço multicast global precisa ser cedido e configurado pela IANA, exigindo um passo burocrático extra.
	\item Muitos roteadores não suportam redirecionamento multicast ou tem esse tipo de tráfego bloqueado como medida de segunça.
	\item O encaminhamento de mensagens de multicast exige muito mais processamento dos roteadores do que uma mensagem unicast.
\end{itemize}

\section{Revisão Bibliográfica}

\subsection{Time, clocks and the ordering of events in a distributed system. L. Lamport, 1978}

Em seu artigo seminal, Lamport~\cite{lamport78} define o conceito de relógios lógicos utilizados para sincronização de sistemas distribuídos. Neste artigo é mostrado um algoritmo distribuído para sincronizar um sistema de relógios lógicos que pode ser utilizado para gerar uma ordenação total de eventos. O uso da ordenação total é ilustrado com um método para resolver problemas de sincronização. O algoritmo é então especializado para sincronizar relógios físicos e se chega um limite da máxima assíncronia entre os relógios.

A ordenação de eventos em sistemas distribuídos é um problema complicado, isto ocorre porque os computadores que formam o sistema não compartilham o mesmo relógio físico, às vezes se torna impossível dizer qual evento ocorreu antes do outro. A relação "aconteceu antes" é apenas uma ordenação parcial dos eventos. Esta relação pode ser definida sem o uso de relógios físicos. Os eventos dentro de um mesmo processo são totalmente ordenados a priori. A relação "aconteceu antes" ($\rightarrow$) pode ser definida pelas 3 regras a seguir:
\begin{enumerate}
	\item Se (a) e (b) ocorrem no mesmo processo, e (a) ocorre antes de (b), então $a\rightarrow b$.
	\item Se (a) é o envio de uma mensagem e (b) é seu recebimento, então $a\rightarrow b$.
	\item Se $a\rightarrow b$ e $b\rightarrow c$ então $a\rightarrow c$. Dois eventos são ditos concorrentes se  $a\leftarrow b$ e $b\leftarrow a$ são ambos falsos.
\end{enumerate}

A relação $a\rightarrow b$ demonstra que "a" pode afetar "b" de maneira causal. Dois eventos são concorrentes se nenhum dos dois pode se afetar causalmente. Esta relação e suas definições permitem ordenar parcialmente os eventos de um sistema distribuído. Relógios lógicos são introduzidos para que se possa gerar uma ordenação total dos eventos. Um relógio lógico é definido como uma função monotonicamente crescente que atribui um valor numérico para cada evento no processo. O valor da função do relógio lógico é o tempo lógico concatenado com o número do processo. Podem ser implementados de maneira simples com contadores, porque tem um significado lógico e não físico. Para um sistema de relógios estar correto a seguinte relação deve valer:

\begin{verse}
	\textbf{Condição de Relógio:} \\ Para quaisquer eventos a e b: se $a\rightarrow b$ então $C(a) < C(b)$.
\end{verse}

Partindo da relação $\rightarrow$ vemos que as duas condições devem valer:
\begin{itemize}
	\item \emph{C1:} Se a e b são processos em $P_{i}$ e a vem antes de b então $C(a) < C(b)$.
	\item \emph{C2:} Se a é o envio de uma mensagem por $P_{i}$ e b é o recebimento desta mensagem por $P_{j}$ então $C(a) < C(b)$.
\end{itemize}

Para garantir que a condição vale temos apenas que utilizar duas regras:
\begin{enumerate}
	\item O processo P incrementa seu relógio a cada evento. Satisfaz C1.
	\item A cada recebimento de uma mensagem de $P_{i}$ por $P_{j}$ contendo timestamp $C_{i}<a>$, $P_{j}$ coloca seu relógio como sendo o máximo entre seu relógio atual e $C_{i}<a>$ e incrementa seu relógio. Satisfaz C2.
\end{enumerate}

Uma relação total dos eventos é obtida apenas utilizando a ordem com que eles ocorrem. A ordenação parcial é acrescida do poder dos relógios lógicos e das regras R1 e R2 além de utilizar o número do processo para desempate, de forma que nunca temos dois eventos simultâneos. Esta ordenação total não necessita ter uma relação direta com a ordem física de ocorrência dos eventos e pode mudar se o sistema de relógios for alterado, mas permanece consistente.

O uso de sistemas de relógios lógicos para resolver problemas de sincronização em sistemas distribuídos é muito importante e é demonstrada por Lamport em seu artigo ao resolver o problema da exclusão mútua distribuída. O algoritmo trabalha com três condições:
\begin{enumerate}
	\item Um processo ao qual foi dado um recurso deve liberá-lo antes que o recurso possa ser dado a outro processo.
	\item Diferentes pedidos de recurso devem ser dados na ordem em que foram pedidos.
	\item Se cada processo ao qual é dado um recurso o libera, então cada pedido é eventualmente tratado.
\end{enumerate}

Estas condições caso respeitadas garantem "safety" e "liveness" para o algoritmo, ou seja, ele é seguro e, eventualmente, progride. Não é trivial resolver este problema de maneira distribuída, sem um gestor de recursos centralizado. Utilizando as regras R1 e R2 podemos estabelecer uma ordenação total entre os pedidos de recurso e o problema fica simples, basta fazer com que os processor fiquem sabendo sobre os pedidos uns dos outros.

Assumimos que os canais de comunicação são FIFO e também que cada mensagem é recebida algum dia. Também assumimos que cada processo pode enviar mensagens diretamente para todos os outros processos, ou seja, o grafo de comunicação é completo. Cada processo mantém sua fila de requisições que não é vista pelos outros processos e que a fila contém inicialmente a mensagem $T_{0}:P_{0}$ requests resource, onde P0 é o processo que ganhou o recurso inicialmente e $T_{0}$ é menor que o valor inicial de qualquer relógio. O algoritmo é definido por 5 regras básicas descritas em pseudo-código abaixo:

\begin{center}
\begin{lstlisting}
/* Algoritmo de Exclusao Mutua de Lamport.
 * O codigo e exatamente o mesmo para todos os processos.
 */

/* Implementa as diretivas de broadcast para envio de mensagens e as diretivas
 * de unicast para envio do ack.
 * Funcoes: sendMessage e sendAckUnicast.
 */
import BcastService;
/* Implementa a fila de mensagens, ordena do mais antigo para o mais recente,
 * assim os pedidos sao respondidos na ordem com que chegaram.
 * Funcoes: put e removeAll.
 */
import MessageQueue;
/* Implementa o relogio logico de Lamport e todas as funcoes de atualizacao
 * e incremento.
 * Funcoes: tickClock e updateClock.
 */
import Clock;

class LamportMutex {

    ProcessNumber pId;
    MessageQueue queue;
    BcastService bcast;    
    Clock clock;

    LamportMutex(ProcessNumber pId) {
        bcast = new BcastService();
        queue = new MessageQueue();
        clock = new Clock();
        this.pId = pId;
        workFlow();
    }

    void workFlow() {
        /* Enquanto o processo estiver em execucao ouve por novos
        * pedidos de acesso e mensagens enviadas por outros processos.
        * Chama as demais funcoes para tratar o recebimento e envio de mensagens.
        */
    }

    Message createMessage(Type t) {
        /* Cria mensagem com tipo pedido, numero do processo
        * e anexa timestamp Tm:Pi */
    }

	/* REGRA 1 */
	void requestResource() {
		/* Avanca o relogio, cria uma mensagem de pedido com timestamp igual ao relogio atual
		* e entao a envia por broadcast para todos os outros processos e apenas no fim
		* insere na fila de mensagens.
		*/
			tickClock();
			Message msg = createMessage(request);
			bcast.sendMessage(msg);
			queue.put(msg);
	}

	/* REGRA 2 */
	void receiveRequestMsg(Message msg) {
		/* Atualiza seu relogio, coloca a mensagem em sua fila de mensagens e envia
		* um ack para o processo que enviou o pedido. Ao fim verifica se pode entrar
		*/
		updateClock(msg.timestamp);
		queue.put(msg);
		bcast.sendAckUnicast(msg.process, msg);
	}

	/* REGRA 3 */
	void releaseResource() {
		/* Saida da regiao critica, atualiza seu relogio, remove todos os pedidos com seu pId
		* que estejam na fila e envia uma mensagem de release para os outros processos.
		*/
		tickClock();
		queue.removeAll(pId);
		msg = createMessage(release);
		bcast.sendMessage(msg);
	}

	/* REGRA 4 */
	void receiveReleaseMsg(Message msg) {
		/* Atualiza o relogio e entao remove as mensagens de pedido de recurso do processo
		* que enviou o release.
		*/
		updateClock(msg.timestamp);
		queue.removeAll(msg.process);
	}

	/* REGRA 5 */
	boolean checkResource() {
		/* Verifica se o seu pedido e o mais antigo e verifica se recebeu
		* pelo menos uma mensagem de cada outro processo com timestamp mais recente
		* que o seu.
		*/
		boolean canAccess = true;
		if (queue.head.process == pId) {
			for each (Process p) {
				if (! queue.containsMsg(p))
					canAccess = false;
			}
		} else {
			canAccess = false;
		}
	}
}
\end{lstlisting}
\end{center}

É fácil demonstrar que o algoritmo satisfaz as condições I-III descritas acima. A condição de que o processo deve ter visto mensagens mais recentes de todos os outros processos, em conjunto com a suposição que as mensagens são recebidas em ordem, garante que o processo Pi conhece todos os outros pedidos que precedem o seu. Como as regras 3 e 4 são as únicas que apagam pedidos da fila é fácil ver que a condição I é satisfeita, ou seja, o processo deve liberar o recurso antes que outro possa utilizá-lo. A condição II é satisfeita vendo que as mensagens do sistema estão totalmente ordenadas, ou seja, todos os processos do sistema enxergam a mesma ordenação dos pedidos. A regra 2 (recebimento de pedido) e a regra 4 (recebimento de release) garantem que o processo irá receber pelo menos uma mensagem mais recente dos outros processos e que todos os pedidos anteriores ao seu eventualmente serão liberados, então chegará um momento em que seu pedido será o mais antigo e será dado acesso, provando a condição III.

Lamport segue demonstrando que o funcionamento deste algoritmo distribuído em que cada processo funciona de maneira independente pode ser representado por uma máquina de estados, formada por um conjunto de comandos e estados. A sincronização da máquina de estados é obtida porque pela ordenação total cada processo ordena sua fila pelos timestamps das mensagens e, portanto, executa a mesma sequência de comandos. Utilizando esta idéia de máquina de estados e o funcionamento básico do algoritmo de sincronização, é possível implementar qualquer forma de sincronização de múltiplos processos em um sistema distribuído. O algoritmo proposto por Herman e Verjus~\cite{herman79} descrito em outra seção segue esta mesma idéia.

No restante do artigo Lamport deriva o uso de relógios lógicos para sincronizar relógios físicos e, através do desenvolvimento de diversas equações, chega em um limite para o atraso máximo entre os relógios ao se utilizar este método de sincronização.

\subsection{An algorithm for maintaining the consistency of multiple copies. D. Herman e J. P. Verjus, 1979}

Herman e Verjus~\cite{herman79} descrevem em seu artigo um algoritmo que, basicamente, estende a idéia de Lamport para implementar consistência entre cópias de dados distribuídos em diversos processos. Os pressupostos para o algoritmo são os mesmos dos fornecidos por Lamport e seu funcionamento é uma estensão ao algoritmo de exclusão mútua dado por Lamport.

O sistema é formado por um conjunto de processos em que cada um deles possui uma cópia do banco de dados, com o objetivo de reduzir o turn-around de cada site e melhorar a disponibilidade do sistema. Com isso os usuários podem requisitar os dados de qualquer um dos bancos e obter o mesmo resultado, sem ter de saber que estão acessando dados de outros servidores, para os usuários a imagem é que os dados estão em apenas um servidor. Este é o mesmo príncipio da replicação para aumento de desempenho em sistemas distribuídos.

Muitos algoritmos foram propostos para garantir consistência mútua de cópias redundantes mas o algoritmo proposto maximiza o paralelismo evitando o uso de locks globais. O sistema trabalha com filas de requisições, levando isso em conta são previstas as seguintes transações:

\begin{itemize}
	\item \emph{Estimativa:} retorna o valor atual do dado no site em questão, não é necessário que seja um valor consistente com os demais sites, é apenas uma estimativa. Pode também ser chamado de leitura rasa.
	\item \emph{Leitura:} o valor só pode ser retornado quando chega ao topo da fila de requisições, isto garante que todas as operações relativas ao item já foram efetuadas.
	\item \emph{Escrita:} a escrita é efetuada quando chega ao topo da fila, é semelhante à leitura.
	\item \emph{Atualização:} uma transação é inserida na fila, e quando ela chega ao topo a fila é travada e é aplicada uma sequência de operações de leitura e escrita, o processo termina quando a mensagem de fim de transação é encontrada.
\end{itemize}

As transações são ordenadas globalmente pelas condições de relógio dadas por Lamport~\cite{lamport78}, utilizando relógios lógicos em cada processo e aplicando-se timestamps às requisições. A ordem de tempo físico não precisa ser obedecida, mas todas as requisições devem ser aplicadas na mesma ordem em todos os sites, de acordo com a ordenação total provida pelos timestamps das requisições.

Consistência neste caso é dada nos termos em que os dados podem diferir ao longo do tempo, mas eventualmente, quando toda atividade cessar, o sistema deve convergir para um estado consistente em que todas as cópias são idênticas. Isto ocorre porque ao fim das atividades as mensagens em trânsito em todo o sistema irão chegar e serão processadas, portanto, em um momento não haverá mais mensagens em trânsito e o sistema terá convergido. A explicação sobre ordenação de requisições é a mesma fornecida por Lamport, seguindo as condições de relógio e regras fornecidas para ordenação total.

A base do algoritmo é um programa em cada site chamado REGISTRAR que obedece um conjunto de regras bem definidas. Ele responde imediatamente a pedidos de estimativa e aplica as regras para os outros tipos de pedidos. O formato das mensagens trocadas entre os processos é a seguinte $<$tipo da mensagem, parâmetros, timestamp$>$. O tipo da mensagem pode também ser uma transação. O algoritmo é descrito em pseudo-código a seguir:

\begin{center}
\begin{lstlisting}
/* Algoritmo de Replicacao consistente de Herman e Verjus,
 * O codigo e exatamente o mesmo para todos os processos.
 */

/* Implementa as diretivas de broadcast para envio de mensagens e as diretivas
 * de unicast para envio do ack e enquiry.
 * Funcoes: sendMessage, sendAckUnicast e sendEnquiry.
 */
import BcastService;
/* Implementa a fila de mensagens, ordena do mais antigo para o mais recente,
 * assim os pedidos sao respondidos na ordem com que chegaram.
 * Funcoes: put e removeAll.
 */
import MessageQueue;
/* Implementa o relogio logico de Lamport e todas as funcoes de atualizacao
 * e incremento.
 * Funcoes: tickClock e updateClock.
 */
import Clock;

class Registrar {

    ProcessNumber pId;
    MessageQueue queue[];
    BcastService bcast;
    Clock clock;
    /* Principal diferenca com relacao a Lamport, dado */
    Data item;
    /* Marca se esta dentro de uma transacao */
    boolean trans = false;

    Registrar(ProcessNumber pId) {
        bcast = new BcastService();
        queue = new MessageQueue[numberProcesses];
        for (int i = 0; i < numberProcesses; i++)
            queue[i] = new MessageQueue();
        clock = new Clock();
        this.pId = pId;
        workFlow();
    }

    void workFlow() {
        /* Enquanto o processo estiver em execucao ouve por novas operacoes
         * e mensagens enviadas por outros processos.
         * Chama as demais funcoes para tratar o recebimento e envio de mensagens.
         * Chama "checkQueues" ao fim de cada iteracao.
         */
    }

    Message getOldest(Queue[] queue) {
        /* Retorna a mensagem mais antiga de todas as filas e faz
         * um pop da mensagem.
         * Se "trans" for true, retorna da mais antiga a mensagem com
         * numero de transacao menor, para respeitar ordem da transacao.
         */
    }

    void checkQueues() {
        /* Verifica por filas vazias e envia enquiries se preciso */
        boolean vazia = false;
        for (Queue q : queue) {
            if (q.empty()) {
                requestEnquiry(q.process);
                vazia = true;
            }
        }

        /* Encontra fila cujo topo tem a mensagem mais antiga */
        if (vazia == false) {
            Message maisAntiga = getOldest(queue);
            switch (maisAntiga.type) {
                case write:
                    item = maisAntiga.value;
                    break;
                case read:
                    maisAntiga.thread.sendValue(item);
                    break;
                case beginUpdate:
                    /* Apenas trata restante das mensagens de transacao */
                    trans = true;
                    break;
                case finishUpdate:
                    /* Marca fim da transacao, elimina estruturas criadas para transacao */
                    trans = false;
                    break;
            }
        }
        
    }

    Message createMessage(Type t) {
        /* Cria mensagem com tipo pedido, numero do processo,
         * e anexa timestamp Tm:Pi */
    }

    Message createMessage(Type t, int value) {
        /* Cria mensagem com tipo pedido, numero do processo,
         * valor e anexa timestamp Tm:Pi */
    }


    void requestWrite(int value) {
        /* Enfileira o pedido de escrita e envia por broadcast
         * para que todos os outros processos possam realizar a
         * escrita.
         */
        tickClock();
        Message msg = createMessage(write, value);
        bcast.sendMessage(msg);
        queue[pId].put(msg);
    }

    void receiveWrite(Message msg) {
        /* Atualiza seu relogio com o timestamp da mensagem e coloca
         * a mensagem em sua fila local.
         */
        updateClock(msg.timestamp);
        queue[pId].put(msg);
    }

    void requestEnquiry(int process) {
        /* Caso a fila do processo J esteja vazia precisa enviar um
         * pedido para o processo para preencher sua fila com um ack.
         */
        tickClock();
        Message msg = createMessage(enquiry);
        bcast.sendEnquiry(msg.process, msg);
    }

    void receiveEnquiry(Message msg) {
        /* Responde a um enquiry enviando um ack para o processo
         * requisitante.
         */
        updateClock(msg.timestamp);
        queue[msg.process].put(msg);
        bcast.sendAckUnicast(msg.process, msg);
    }

    void receiveAck(Message msg) {
        /* Enfileira o ack do processo */
        updateClock(msg.timestamp);
        queue[msg.process].put(msg);
    }

    void requestRead() {
        /* Avanca o relogio, cria a mensagem de leitura e a enfileira
         * em sua fila, nao precisa enviar para os outros porque nao
         * altera os dados.
         */
        tickClock();
        Message msg = createMessage(read);
        queue[pId].put(msg);
    }

    void requestUpdate(List<Message> operations) {
        /* Avanca o relogio, enfileira mensagem de inicio de atualizacao,
         * operacoes e mensagem de fim de atualizacao. Todas as mensagens da
         * transacao tem o mesmo timestamp, diferindo apenas seu numero, com isso
         * se consegue que elas sejam executadas em conjunto sem nenhuma outra operacao
         * entre elas. A lista operacoes contem todas as operacoes da transacao.
         */
        tickClock();
        /* Envia mensagem de inicio da atualizacao. Tipo beginUpdate */
        bcast.sendMessage(operations.head);
        queue[pId].put(operations.head);
        for (Message msg : operations) {
            /* So precisa enviar mensagens de write */
            if (msg.type == write)
                bcast.sendMessage(msg);
            queue[pId].put(msg);
        }
        /* Envia a mensagem de fim da atualizacao. Tipo finishUpdate */
        bcast.sendMessage(operations.tail);
        queue[pId].put(operations.tail);
    }
}
\end{lstlisting}
\end{center}

Pela ordenação total das mensagens no sistema e pelo fato de que as mensagens enviadas por um mesmo processo são entregues na ordem com que foram enviadas, a prova de consistência é a mesma dada por Lamport~\cite{lamport78}. Dada a característica FIFO dos canais basta que o site conheça pelo menos uma mensagem de cada um dos outros sites para poder decidir se pode ou não processar alguma requisição, para isto entram em ação as mensagens de enquiry e acknowledge.

\subsection {Distributed snapshots: determining global
states of distributed systems. K. Chandy e L. Lamport, 1985}

Chandy e Lamport~\cite{chandy85} apresentam um algoritmo para determinar um estado global de um sistema distribuído. Para isso, um processo precisa recolher os estados locais salvos por cada um dos outros processos, incluindo a si mesmo, montando assim um estado global. O problema está no fato de que os processos, sem acesso a memória ou relógio compartilhados, não são capazes de capturar seu estado ao mesmo tempo, portanto, a captura de um estado global consistente não é trivial.

Neste artigo, um processo é definido como uma coleção de estados, sendo um deles o inicial, e uma cadeia de eventos. Um evento é uma operação atômica que pode mudar o estado do processo e o estado de no máximo um canal ligado a este processo (por troca de mensagens). Os canais são direcionados, portanto um canal que pode enviar e receber mensagens é modelado como dois canais. Um estado global é uma coleção formada pelos estados dos processos e canais do sistema.

O algoritmo funciona da seguinte maneira: cada processo salva o seu estado, e os processos colaboram para salvar os estados dos canais entre eles. Como é impossível garantir que cada estado foi salvo ao mesmo tempo, o problema é garantir que o estado global salvo é consistente e tem algum significado com o estado realmente atingido.

Consideramos um canal C de P a Q, N o número de mensagens enviadas através de C antes que o estado de P foi salvo e N' o número de mensagens enviadas através de C antes que o estado do canal C foi salvo. Caso N seja diferente de N', é simples perceber que o estado global pode ser inconsistente, pois algum evento de recebimento foi notado por P e o envio não foi notado por Q ou vice-versa. Analogamente podemos chegar à mesma conclusão considerando as mensagens recebidas por Q. Considerando isso e o fato que o número de mensagens enviadas tem que ser maior ou igual ao número de mensagens recebidas, pode-se entender a motivação para o algoritmo.

Para apenas dois nós, o algoritmo funciona da seguinte maneira: o processo P inicia enviando um marcador pelo canal C (logo após salvar seu estado, e antes de enviar qualquer outra mensagem) e começa a gravar o estado do canal. Quando Q recebe o marcador, ele salva o seu estado e antes de enviar qualquer outra mensagem e salva o estado do canal incidente como vazio. O processo Q então envia um marcado de volta para P e quando P recebe o marcador ele termina de gravar o estado do canal. Quando Q enviar para P seu estado e o estado amostrado do canal incidente, P será capaz de formar, em conjunto com as informações salvas por ele, um snapshot de um estado consistente do sistema. 

O funcionamento do algoritmo impede que o sistema tenha amostras de mensagens que foram recebidas por um processo e não tenha amostras de seu envio. Isto ocorre porque nenhuma nova mensagem é enviada entre o salvamento do estado e o envio de um novo marcador e porque o canal de comunicação é FIFO, ou seja, qualquer mensagem que tenha sido amostrada como sendo recebida por um processo será amostrada ou no estado do processo que a enviou ou no estado do canal incidente. Com isto, as propriedades de consistência definidas no parágrafo anterior são garantidas.

\begin{center}
\begin{lstlisting}
/* Algoritmo de Deteccao de Estados Globais de 
 * Chandy e Lamport. O codigo e o mesmo para 
 * todos os processos.
 */
class DistributedSnapshot {

    BcastService bcast;
    ProcessNumber parent;

	DistributedSnapshot() {
		bcast = new BcastService();
		workFlow();
	}

	void workFlow() {
		/* Enquanto o processo estiver em execucao, ouve 
		 * por chegada de marcadores e pela chamada ao snapshot. 
		 * Apos o processo ter recebido ou enviado marcadores 
		 * por todos os canais, o algoritmo terminou entao ele envia 
		 * os estados que salvou e todos os que receber para o 
		 * seu processo pai. */
	}

	State recordProcessState() {
		/* Salva o estado atual do Processo considerando todas as mensagens que ja recebeu */
	}

	State recordChannelState(channel c, flag empty) {
		/* Salva o estado atual do Canal de entrada considerando 
		 * todas as mensagens que recebeu entre o salvamento 
		 * do estado do processo e o recebimento do marcador 
		 * por aquele canal. Se a flag empty for verdadeira, 
		 * salva o estado do canal como vazio. */
	}

	void snapshot() {
		/* Funcao que dispara a captura do estado global. 
		 * E disparada por um processo externo ao sistema,
		 * como um timer ou acao de algum usuario. */
		State procState = recordProcessState();
		parent = this.pID;
		sendMarker();
	}

	/* REGRA 1. Send Marker */
	void sendMarker() {
		/* Para todos os canais de saida do processo atual,
		 * envia um marcador pelo canal 
		 * apos salvar seu proprio estado. */
		for each (channel c) {
			bcast.sendUnicast(c.toProccess, marker)
		}
	}

	/* REGRA 2. Receive Marker */
	void receiveMarker(channel c){
		/* Quando um processo recebe o primeiro marcador,
         * ele salva seu proprio estado e o estado do canal 
		 * pelo qual ele recebeu o marcador como vazio. Caso 
		 * esse nao seja o primeiro marcador, salva o estado 
		 * do canal sendo as mensagens que recebeu pelo 
		 * canal depois do salvamento de seu proprio estado. */
		if (first marker) {
			parent = c.fromProcess.pID;
			recordProccessState();
			recordChannelState(c,true);
		} else {
			recordChannelState(c,false);
		}
	}
}
\end{lstlisting}
\end{center}

Para que o algoritmo termine, é necessário que nenhum marcador fique parado indefinidamente em algum canal, e que haja um caminho dos processos que salvam o seu estado espontaneamente para todos os outros processos, assegurando assim que todos os processos recebam um ou mais marcadores e salvem seu estado, pois se nenhum marcador fica parado em um canal indefinidamente e os processos sempre enviam marcadores por todos os canais, certamente todos os processos salvarão seus estados.

O Estado global salvo pelo algoritmo não é necessariamente um estado global pelo qual a computação atual passou. No artigo é mostrado, porém, que dado uma computação onde S$_{0}$ é o estado inicial e S$_{1}$ é o estado final, o estado salvo pelo algoritmo S$^{*}$, é atingível a partir do estado inicial S$_{0}$, e o estado final S$_{1}$ é atingível a partir de S$^{*}$.

Para mostrar este fato, primeiro demonstra-se que é possível obter uma sequência de eventos onde todos os eventos que aconteceram antes de os processos salvarem os seus estados, a pré-captura, ocorrem antes dos eventos pós-captura de estado. Isso se deve ao fato que se algum evento pós-captura ocorre antes de algum evento pré-captura em alguma permutação da computação, estes eventos são necessariamente concorrentes, pois não é possível um evento pós-captura ocorrer antes de um evento pré-captura no mesmo processo. É relativamente fácil mostrar que S$^{*}$ é o estado capturado pelo algoritmo, o que mostra que o estado global capturado é possível em uma dada computação do sistema distribuído.

\begin{thebibliography}{99}

\bibitem{chandy85}
M.~Chandy and L.~Lamport.
\newblock Distributed snapshots: determining global states of distributed
  systems.
\newblock {\em Transactions on Computer Systems}, 3(1):63--75, 1985.

\bibitem{herman79}
D.~Herman and J.~P. Verius.
\newblock An algorithm for maintaining the consistency of multiple copies.
\newblock In {\em Proceedings of the 1st International Conference on
  Distributed Computing Systems}, pages 625--631, Washington, DC, 1979. IEEE
  Computer Society.

\bibitem{lamport78}
L.~Lamport.
\newblock Time, clocks, and the ordering of events in a distributed system.
\newblock {\em Communications of the ACM}, 21(7):558--565, July 1978.

\end{thebibliography}

\end{document}
